package tarantula.instrumenter;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import soot.G;
import soot.Pack;
import soot.PackManager;
import soot.Scene;
import soot.SootClass;
import soot.Transform;
import tarantula.util.FileUtils;
import tarantula.util.Globals;

public class TestJUnitMain extends junit.framework.TestCase {
	
	public void testJimpleJUnitIntrumenter() {
		// ***IMPORTANT: set proper directories before running the test.***
		String jimpleDirPath = "G:/72_Simplify/workspace_simplify/TarantulaJ/testdata/dataset2/jimple/";
		String extraClassPath = "G:/72_Simplify/workspace_simplify/apache-commons-collections-2.0/bin/";
		// "G:/72_Simplify/workspace_simplify/TarantulaJ/bin/";

		String includePkg = "org.apache.";

		String junit_input_dir = "G:/72_Simplify/workspace_simplify/apache-commons-collections-2.0/test_bin/";
		String junit_output_dir = "G:/72_Simplify/workspace_simplify/apache-commons-collections-2.0/instrumented/";
		String junit_output_format = "J";

		/* add a phase to transformer pack by call Pack.add */
		Pack jtp = PackManager.v().getPack("jtp");
		jtp.add(new Transform("jtp.junitinstrumenter",
				new JUnitTestInstrumenter()));

		// call the instrumenter
		List<String> sootArgs = new LinkedList<String>();

		sootArgs.add("-prepend-classpath");
		sootArgs.add("-keep-line-number");
		sootArgs.add("-soot-class-path");
		sootArgs.add("." + Globals.pathSep + junit_input_dir + Globals.pathSep
				+ Globals.getClassPath() + Globals.pathSep + extraClassPath);
		System.out.println("." + Globals.pathSep + junit_input_dir
				+ Globals.pathSep + Globals.getClassPath());
		sootArgs.add("-output-format");
		sootArgs.add(junit_output_format);
		if (includePkg != null) {
			sootArgs.add("-i");
			sootArgs.add(includePkg);
		}
		sootArgs.add("-process-dir");
		sootArgs.add(junit_input_dir);
		sootArgs.add("-output-dir");
		sootArgs.add(junit_output_dir);

		Scene.v().addBasicClass("tarantula.instrumenter.Tracer",
				SootClass.SIGNATURES);
		soot.Main.main(sootArgs.toArray(new String[0]));

		G.v().reset();

		// test the generated Jimple files
		Set<File> expectedJimpleFiles = FileUtils.getJimpleFiles(jimpleDirPath);
		Set<File> actualJimpleFiles = FileUtils.getJimpleFiles(junit_output_dir);

		assertEquals("The number of generated files is unexpected.",
				expectedJimpleFiles.size(), actualJimpleFiles.size());

		boolean isPassed = true;
		StringBuilder errorMessage = new StringBuilder();

		for (File expectedFile : expectedJimpleFiles) {
			boolean tmpResult = checkFiles(expectedFile, actualJimpleFiles);
			isPassed = isPassed && tmpResult;

			if (!tmpResult) {
				errorMessage.append("Cannot find equal file for: ");
				errorMessage.append(expectedFile.getName());
				errorMessage.append("\n");
			}
		}

		// clear the temporary output directory
		FileUtils.clearJimpleFiles(junit_output_dir);

		assertTrue(errorMessage.toString(), isPassed);
	}

	public void testClassJUnitInstrumenter() throws IOException,
			InterruptedException {
		// ***IMPORTANT: set proper directories before running the test.***
		String expectedStdOutputPath = "G:/72_Simplify/workspace_simplify/TarantulaJ/testdata/dataset2/stdOutput";
		String extraClassPath = "G:/72_Simplify/workspace_simplify/TarantulaJ/testdata/dataset2/jars/apache-commons-collections-2.0-appbin.jar";
		String tarantulajPath = "G:/72_Simplify/workspace_simplify/TarantulaJ/bin/";
		String junitLibPath = "G:/72_Simplify/workspace_simplify/PlaySootInstru/lib/junit-4.9b3.jar";
		String includePkg = "org.apache.";
		String junit_input_dir = "G:/72_Simplify/workspace_simplify/TarantulaJ/testdata/dataset2/jars/apache-commons-collections-2.0-testbin.jar";
		String junit_output_dir = "G:/72_Simplify/workspace_simplify/apache-commons-collections-2.0/instrumented/";
		String junit_output_format = "c";

		/* add a phase to transformer pack by call Pack.add */
		Pack jtp = PackManager.v().getPack("jtp");
		jtp.add(new Transform("jtp.junitinstrumenter",
				new JUnitTestInstrumenter()));

		// call the instrumenter
		List<String> sootArgs = new LinkedList<String>();

		sootArgs.add("-prepend-classpath");
		sootArgs.add("-keep-line-number");
		sootArgs.add("-soot-class-path");
		sootArgs.add("." + Globals.pathSep + junit_input_dir + Globals.pathSep
				+ Globals.getClassPath() + Globals.pathSep + extraClassPath);
		System.out.println("." + Globals.pathSep + junit_input_dir
				+ Globals.pathSep + Globals.getClassPath());
		sootArgs.add("-output-format");
		sootArgs.add(junit_output_format);
		if (includePkg != null) {
			sootArgs.add("-i");
			sootArgs.add(includePkg);
		}
		sootArgs.add("-process-dir");
		sootArgs.add(junit_input_dir);
		sootArgs.add("-output-dir");
		sootArgs.add(junit_output_dir);

		Scene.v().addBasicClass("tarantula.instrumenter.Tracer",
				SootClass.SIGNATURES);
		soot.Main.main(sootArgs.toArray(new String[0]));

		G.v().reset();

		/* run the instrumented code */
		// compose the command line (currently Windows only...)
		StringBuilder cpStr = new StringBuilder(".");
		cpStr.append(Globals.pathSep);
		cpStr.append(extraClassPath);
		cpStr.append(Globals.pathSep);
		cpStr.append(junitLibPath);
		cpStr.append(Globals.pathSep);
		cpStr.append(tarantulajPath);

		List<String> cmdList = new ArrayList<String>();
		cmdList.add("cmd");
		cmdList.add("/C");
		cmdList.add("java");
		cmdList.add("-cp");
		cmdList.add(cpStr.toString());
		// we have actually commented out most of the test suites
		// in the source code of TestAll, leaving only one for test.
		cmdList.add("org.apache.commons.collections.TestAll");

		// launch the test process
		ProcessBuilder pb = new ProcessBuilder(cmdList.toArray(new String[0]));
		pb.directory(new File(junit_output_dir));
		Process process = pb.start();

		// get the output and store it into two separate tmp files
		InputStream stdStream = process.getInputStream();
		InputStream errStream = process.getErrorStream();
		BufferedReader stdReader = new BufferedReader(new InputStreamReader(
				stdStream));
		BufferedReader errReader = new BufferedReader(new InputStreamReader(
				errStream));

		File tmpStdFile = new File(junit_output_dir + Globals.fileSep
				+ "tmpStdOutput.tmp");
		File tmpErrFile = new File(junit_output_dir + Globals.fileSep
				+ "tmpErrOutput.tmp");
		PrintWriter stdWriter = new PrintWriter(tmpStdFile);
		PrintWriter errWriter = new PrintWriter(tmpErrFile);

		String stdLine = stdReader.readLine();
		while (stdLine != null) {
			stdWriter.println(stdLine);
			stdLine = stdReader.readLine();
		}

		String errLine = errReader.readLine();
		while (errLine != null) {
			errWriter.println(errLine);
			errLine = errReader.readLine();
		}

		stdWriter.close();
		errWriter.close();
		stdReader.close();
		errReader.close();

		process.waitFor();

		// check the standard output stream
		// (for this specific case, we do have to check standard error stream)
		// Note that we do not check all the content, because in the error
		// messages
		// of some exceptions there may be some internationalized texts...
		// So we just check the summary, such as
		// "Tests run: 53,  Failures: 2,  Errors: 2".
		assertTrue(
				"The test summary is unexpected.",
				checkTestSummaryLine(expectedStdOutputPath,
						tmpStdFile.getAbsolutePath()));

		// TODO: check the output of Tracer.flushAndClean()
		// This will be finished when the Tracer.flushAndClean() and relevant
		// instrumentation (e.g., every statement instrumenter) are finished and
		// tested.

		FileUtils.clearTmpDir(new File(junit_output_dir));
	}

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		try {
			TestJUnitMain test = new TestJUnitMain();
			test.testJimpleJUnitIntrumenter();
			test.testClassJUnitInstrumenter();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	private boolean checkTestSummaryLine(String expectedPath, String actualPath) {
		boolean isIdentical = false;
		try {
			BufferedReader actualReader = new BufferedReader(new FileReader(
					new File(actualPath)));
			BufferedReader expectedReader = new BufferedReader(new FileReader(
					new File(expectedPath)));

			String actualSummary = null;
			String lineInActual = actualReader.readLine();
			while (lineInActual != null) {
				if (!lineInActual.trim().equals("")) { // retain the last
														// non-empty line
					actualSummary = lineInActual;
				}
				lineInActual = actualReader.readLine();
			}

			String expectedSummary = null;
			String lineInExpected = expectedReader.readLine();
			while (lineInExpected != null) {
				if (!lineInExpected.trim().equals("")) { // retain the last
															// non-empty line
					expectedSummary = lineInExpected;
				}
				lineInExpected = expectedReader.readLine();
			}

			if (expectedSummary == null && actualSummary == null) {
				isIdentical = true;
			} else if (expectedSummary != null && actualSummary != null) {
				if (expectedSummary.equals(actualSummary)) {
					isIdentical = true;
				}
			}
			// else { isIdentical = false; }

			actualReader.close();
			expectedReader.close();
		} catch (IOException ex) {
			ex.printStackTrace();
		}

		return isIdentical;
	}
	
	private boolean checkFiles(File expectedFile, Set<File> actualFiles) {
		File counterpartFile = null;
		for (File af : actualFiles) {
			if (af.getName().equals(expectedFile.getName())) {
				counterpartFile = af;
				break;
			}
		}

		if (counterpartFile == null) {
			return false;
		} else {
			return checkEqualityOfTextFiles(expectedFile, counterpartFile);
		}
	}

	private boolean checkEqualityOfTextFiles(File expectedFile, File actualFile) {
		if (expectedFile.length() != actualFile.length()) {
			return false;
		} else {
			boolean isContentEqual = false;

			try {
				BufferedReader eReader = new BufferedReader(new FileReader(
						expectedFile));
				BufferedReader aReader = new BufferedReader(new FileReader(
						actualFile));

				String expectedLine = eReader.readLine();
				while (expectedLine != null) {
					String actualLine = aReader.readLine();
					if (actualLine != null) {
						if (!expectedLine.equals(actualLine)) {
							break;
						}
					} else {
						break;
					}
					expectedLine = eReader.readLine();
				}

				isContentEqual = true;

				eReader.close();
				aReader.close();
			} catch (IOException ex) {
				ex.printStackTrace();
			}

			return isContentEqual;
		}
	}

}
